#include "tiny_obj_loader.h"
#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc
// Optional. define TINYOBJLOADER_USE_MAPBOX_EARCUT gives robust triangulation. Requires C++11
// #define TINYOBJLOADER_USE_MAPBOX_EARCUT
#include "OBJLoader.h"
namespace SL
{ // Hash function for vertex deduplication

struct VertexHash
{
    std::size_t operator()(const Vertex& vertex) const
    {
        std::size_t h1 = std::hash<float>{}(vertex.position[0]);
        std::size_t h2 = std::hash<float>{}(vertex.position[1]);
        std::size_t h3 = std::hash<float>{}(vertex.position[2]);
        std::size_t h4 = std::hash<float>{}(vertex.uv[0]);
        std::size_t h5 = std::hash<float>{}(vertex.uv[1]);
        return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3) ^ (h5 << 4);
    }
};

bool OBJLoader::loadOBJ(const std::string& inputfile)
{
    tinyobj::ObjReaderConfig reader_config;
    reader_config.mtl_search_path = "./"; // Path to material files

    tinyobj::ObjReader reader;
    if (!reader.ParseFromFile(inputfile, reader_config))
    {
        if (!reader.Error().empty())
        {
            std::cerr << "TinyObjReader: " << reader.Error();
        }
        exit(1);
    }

    if (!reader.Warning().empty())
    {
        std::cout << "TinyObjReader: " << reader.Warning();
    }
    fill_members(reader);

    return true;
}

void OBJLoader::fill_members(tinyobj::ObjReader& reader)
{
    auto& attrib = reader.GetAttrib();
    auto& shapes = reader.GetShapes();
    auto& materials = reader.GetMaterials();

    // std::cout << "vert " << attrib.GetVertices().size() / 3 << std::endl;
    // std::cout << "uv " << attrib.texcoords.size() / 2 << std::endl;
    auto& verts = attrib.GetVertices();
    int num = verts.size() / 3;
    vertices.reserve(num);
    /*
    OBJ is Right-handed Z-up

        Y
        |
        |
        O ---- X
       /
      / Z

    OURS is Light-handed Y-up

        Y
        | / Z
        |/
        O ---- X

    */
    float3 center(0,0,0);
    for (int i = 0; i < num; ++i)
    {
        Vertex tmp;
        // swap Y, Z
        tmp.position = float3(verts[i * 3], verts[i * 3 + 1], -verts[i * 3 + 2]);
        // center += tmp.position;
        vertices.push_back(std::move(tmp));
    }
    // center /=  vertices.size();

    // Loop over shapes
    for (size_t s = 0; s < shapes.size(); s++)
    {
        // Loop over faces(polygon)
        size_t index_offset = 0;
        for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
        {
            size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]);

            // Loop over vertices in the face.

            // reverse wind order
            // for (int v = fv - 1; v >= 0; v--)
            for (size_t v = 0; v < fv; v++)
            {
                // access to vertex
                tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
                // tinyobj::real_t vx = attrib.vertices[3 * size_t(idx.vertex_index) + 0];
                // tinyobj::real_t vy = attrib.vertices[3 * size_t(idx.vertex_index) + 1];
                // tinyobj::real_t vz = attrib.vertices[3 * size_t(idx.vertex_index) + 2];

                indices.push_back(idx.vertex_index);
                if (idx.texcoord_index >= 0)
                    vertices[idx.vertex_index].uv =
                        float2(attrib.texcoords[2 * idx.texcoord_index], attrib.texcoords[2 * idx.texcoord_index + 1]);

                // Check if `normal_index` is zero or positive. negative = no normal data
                if (idx.normal_index >= 0)
                {
                    // normal need flip too
                    vertices[idx.vertex_index].normal =
                        float3(attrib.normals[3 * idx.normal_index], attrib.normals[3 * idx.normal_index + 1],-attrib.normals[3 * idx.normal_index + 2]);
                    // tinyobj::real_t nx = attrib.normals[3 * size_t(idx.normal_index) + 0];
                    // tinyobj::real_t ny = attrib.normals[3 * size_t(idx.normal_index) + 1];
                    // tinyobj::real_t nz = attrib.normals[3 * size_t(idx.normal_index) + 2];
                }

                // Check if `texcoord_index` is zero or positive. negative = no texcoord data
                // if (idx.texcoord_index >= 0)
                // {
                //     tinyobj::real_t tx = attrib.texcoords[2 * size_t(idx.texcoord_index) + 0];
                //     tinyobj::real_t ty = attrib.texcoords[2 * size_t(idx.texcoord_index) + 1];
                // }

                // Optional: vertex colors
                // tinyobj::real_t red   = attrib.colors[3*size_t(idx.vertex_index)+0];
                // tinyobj::real_t green = attrib.colors[3*size_t(idx.vertex_index)+1];
                // tinyobj::real_t blue  = attrib.colors[3*size_t(idx.vertex_index)+2];
            }
            index_offset += fv;

            // per-face material
            // shapes[s].mesh.material_ids[f];
        }
    }
}
}